﻿using System;
using System.Windows.Input;
using MvvmFx.Common.ViewModels.Behaviors.Observable;

namespace MvvmFx.Common.ViewModels.Commands
{
    /// <summary>
    /// The case ViewModelCommand implements ICommand and allows
    /// ViewModel-level functionality to execute when the commmand
    /// is executed.
    /// </summary>
    /// <remarks>
    /// Inherit from this class to create an encapsulated unit of application
    /// logic that should be executed when the Exceute method of its <see cref="ICommand"/>
    /// interface is executed.
    /// Override the abstract protected OnExecute method to implement the unit of
    /// code that should be executed. 
    /// Override the virtual protected OnCanExecute method
    /// to specify custom logic to be evaluated when the CanExecute method of its <see cref="ICommand"/>
    /// interface is called.
    /// Call the protected OnCanExecuteChanged or the public RaiseCanExecuteChanged
    /// methods to raise the CanExecuteChanged event of its <see cref="ICommand"/>
    /// interface, and have the CanExecute method re-evaluated. 
    /// </remarks>
    /// <example>
    /// The example below shows a concrete implementation of a ViewModelCommand.
    /// It overrides the OnExecute and OnCanExecute methods to specify
    /// the code to run when the command is executed, and the code to evaluate whether
    /// the command can execute.
    /// It also leverages the behaviour of the <see cref="ObservableViewModel"/> class
    /// to specify when that the CanExecute method should be re-evaluated when a certain 
    /// property changes.
    /// <code>
    ///   public class SaveProcessCommand : ViewModelCommand
    ///   {
    ///    private readonly ProcessService processService;
    ///
    ///    public SaveProcessCommand(ProcessService processService)
    ///    {
    ///        this.processService = processService;
    ///
    ///        // When the CurrentStep property of processService changes,
    ///        // re-evaluate the CanExecute method.
    ///        processService.AddHandler("CurrentStep", OnCanExecuteChanged);
    ///    }
    ///
    ///    protected override void OnExecute(object param)
    ///    {
    ///        processService.Save();
    ///    }
    ///
    ///    protected override bool OnCanExecute(object param)
    ///    {
    ///        return processService.State.IsModified;
    ///    }
    /// }
    /// </code>
    /// </example>
    public abstract class ViewModelCommand : Command, IViewModelCommand
    {
        #region [ Events ]

        /// <summary>
        /// The can execute changed event of type <see cref="EventHandler"/>.
        /// </summary>
        public event EventHandler CanExecuteChanged;

        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="ViewModelCommand"/> class.
        /// </summary>
        protected ViewModelCommand() { }

        #region [ Execute Public Methods ]

        /// <summary>
        /// Execute the specified specified command logic.
        /// </summary>
        public void Execute()
        {
            OnExecute(null);

            NotifyTypeHandlers(null);
            NotifyInstanceHandlers(null);
        }

        /// <summary>
        /// Execute the specified specified command logic.
        /// </summary>
        /// <param name="parameter">The object to be passed to the command execute function.</param>
        public void Execute(object parameter)
        {
            OnExecute(parameter);

            NotifyTypeHandlers(parameter);
            NotifyInstanceHandlers(parameter);
        }

        /// <summary>
        /// Public method to raise the OnCanExecuteChanged event from the outside
        /// </summary>
        public void RaiseCanExecuteChanged()
        {
            OnCanExecuteChanged();
        }

        #endregion

        #region [ ICommand Public Methods ]

        /// <summary>
        /// Can execute check for the specified command.
        /// </summary>
        /// <param name="parameter">The object to be passed to the command can execute function.</param>
        /// <returns>If the command can be executed.</returns>
        bool ICommand.CanExecute(object parameter)
        {
            return this.OnCanExecute(parameter);
        }

        /// <summary>
        /// Execute the specified specified command logic.
        /// </summary>
        /// <param name="parameter">The object to be passed to the command execute function.</param>
        void ICommand.Execute(object parameter)
        {
            this.Execute(parameter);
        }

        #endregion

        #region [ CanExecute Protected Methods ]

        /// <summary>
        /// Default can execute function to be called.
        /// </summary>
        /// <remarks>
        /// Most commands are enabled most of the time on this model.
        /// </remarks>
        /// <param name="parameter">The object to be passed to the command can execute function.</param>
        /// <returns>Default return value of True.</returns>
        protected virtual bool OnCanExecute(object parameter)
        {
            return true;
        }

        /// <summary>
        /// Notify the of the can execute changing.
        /// </summary>
        /// <remarks>
        /// Override in derived class to provide additional logic to deteremine
        /// if the command can be executed.
        /// </remarks>
        protected void OnCanExecuteChanged()
        {
            EventHandler tempHandler = CanExecuteChanged;
            if (tempHandler != null)
            {
                tempHandler(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Occurs when the <see cref="ICommand.CanExecuteChanged"/> changes.  Deteremin if the command
        /// can be executed.
        /// </summary>
        /// <param name="sender">The <see cref="object"/> object that fired the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> event fired.</param>
        protected void OnCanExecuteChanged(object sender, EventArgs e)
        {
            this.OnCanExecuteChanged();
        }

        #endregion

        #region [ Execute Protected Methods ]

        /// <summary>
        /// Override in derived class to provide the logic to be excuted.
        /// </summary>
        /// <param name="parameter">The parameter required to execute a unit of logic.</param>
        protected abstract void OnExecute(object parameter);

        #endregion

        #region [ IDisposable ]

        /// <summary>
        /// Override in derived classes to clean up specific resources in the concrete class.
        /// </summary>
        protected virtual void OnDispose()
        {
            RemoveHandlers();
        }

        #endregion
    }

    /// </summary>
    /// The case ViewModelCommand<T> implements ICommand and allows
    /// ViewModel-level functionality to execute when the commmand
    /// is executed.
    /// The ViewModelCommand<T> class provides a base class to define a view model command 
    /// for an associated <see cref="CommandHandler{T}"/> class. Ideally used as saver commands
    /// or adding handler(s) <see cref="ViewModelBase"/>. 
    /// </summary>
    /// <remarks>
    /// Inherit from this class to create an encapsulated unit of application
    /// logic that should be executed when the Exceute method of its <see cref="ICommand"/>
    /// interface is executed.
    /// Override the abstract protected OnExecute method to implement the unit of
    /// code that should be executed. 
    /// Override the virtual protected OnCanExecute method
    /// to specify custom logic to be evaluated when the CanExecute method of its <see cref="ICommand"/>
    /// interface is called.
    /// Call the protected OnCanExecuteChanged or the public RaiseCanExecuteChanged
    /// methods to raise the CanExecuteChanged event of its <see cref="ICommand"/>
    /// interface, and have the CanExecute method re-evaluated. 
    /// </remarks>
    /// <example>
    /// The example below shows a concrete implementation of a ViewModelCommand.
    /// It overrides the OnExecute and OnCanExecute methods to specify
    /// the code to run when the command is executed, and the code to evaluate whether
    /// the command can execute.
    /// It also leverages the behaviour of the <see cref="ObservableViewModel"/> class
    /// to specify when that the CanExecute method should be re-evaluated when a certain 
    /// property changes.
    /// <code>
    ///   public class SaveProcessCommand : ViewModelCommand
    ///   {
    ///    private readonly ProcessService processService;
    ///
    ///    public SaveProcessCommand(ProcessService processService)
    ///    {
    ///        this.processService = processService;
    ///
    ///        // When the CurrentStep property of processService changes,
    ///        // re-evaluate the CanExecute method.
    ///        processService.AddHandler("CurrentStep", OnCanExecuteChanged);
    ///    }
    ///
    ///    protected override void OnExecute(object param)
    ///    {
    ///        processService.Save();
    ///    }
    ///
    ///    protected override bool OnCanExecute(object param)
    ///    {
    ///        return processService.State.IsModified;
    ///    }
    /// }
    /// </code>
    /// </example>
    /// <typeparam name="TViewModel">The view model associated to the command.</typeparam>
    public abstract class ViewModelCommand<TViewModel>
        : ViewModelCommand where TViewModel : ViewModelBase
    {
        /// <summary>
        /// The view model field.
        /// </summary>
        private readonly TViewModel viewModel;

        /// <summary>
        /// Initializes a new instance of the <see cref="ViewModelCommand&lt;TViewModel&gt;"/> class.
        /// </summary>
        /// <param name="viewModel">The view model associated to the command.</param>
        protected ViewModelCommand(TViewModel viewModel)
        {
            this.viewModel = viewModel;
        }

        /// <summary>
        /// Gets the value of the <see cref="ViewModel"/> property.
        /// </summary>
        public TViewModel ViewModel
        {
            get { return this.viewModel; }
        }
    }
}